import { dom, EventHandler, Nodes } from '@knno/dom';
import { FormItem } from '../interface';
import { inputStyle } from './input.css';
import ClearIcon from '@material-design-icons/svg/filled/clear.svg';
import EyeIcon from '@material-design-icons/svg/filled/visibility.svg';
import EyeOffIcon from '@material-design-icons/svg/filled/visibility_off.svg';
import { takeIf } from '../../tools/sugar';
import { Validatable } from '../validatable';
import { DATA } from '../symbol';

export type InputType =
	| 'text'
	| 'number'
	| 'search'
	| 'email'
	| 'tel'
	| 'url'
	| 'date'
	| 'time'
	| 'datetime-local'
	| 'week'
	| 'month'
	| 'password';

export interface InputEventMap {
	prefixiconclick: Event;
	suffixiconclick: Event;
	clear: InputEvent;
	enter: InputEvent;
}
export type InputEventKey = keyof InputEventMap;
export type InputEventHandler<T extends InputEventKey, O extends Nodes> = (this: O, event: InputEventMap[T]) => void;

export type ValidationItem = {
	exp: RegExp;
	message: string;
};

export interface Input {
	on<K extends keyof InputEventMap>(
		event: K,
		listener: InputEventHandler<K, this>,
		options?: boolean | AddEventListenerOptions
	): this;
	on<K extends keyof HTMLElementEventMap>(
		event: K,
		listener: EventHandler<K, this>,
		options?: boolean | AddEventListenerOptions
	): this;
	off<K extends keyof InputEventMap>(
		event: K,
		listener: InputEventHandler<K, this>,
		options?: boolean | AddEventListenerOptions
	): this;
	off<K extends keyof HTMLElementEventMap>(
		event: K,
		listener: EventHandler<K, this>,
		options?: boolean | AddEventListenerOptions
	): this;
}

export class Input extends Validatable implements FormItem {
	protected [DATA] = { shown: false };
	constructor() {
		const input = dom.input();
		const root = dom
			.div(inputStyle)
			.append(input)
			.on('mousedown', () => {
				if (!this.disabled()) {
					setTimeout(() => {
						input.focus();
					});
				}
			});
		super(root);
		input.on('input', () => {
			this.invalid(false);
			this.showClear();
		});
		input.on('keypress', (evt) => {
			if (evt.key === 'Enter') {
				this.emit(new InputEvent('enter'));
			}
		});
		input.on('change', () => {
			this.invalid(false);
		});
		input.on('focus', () => {
			this.addClass('focus');
			this.showClear();
			this.emit(new Event('focus'));
		});
		input.on('blur', () => {
			this.removeClass('focus');
			this.showClear();
			this.emit(new Event('blur'));
		});
	}

	clear(): this {
		this.value('');
		return this;
	}

	private showClear() {
		const svg = this.query('svg[name=clear]');
		if (this.clearable() && !this.readonly() && !this.disabled() && this.text()) {
			if (svg.length === 0) {
				const input = this.query('input');
				dom
					.html(ClearIcon)
					.name('clear')
					.appendTo(this.elems(), input)
					.on('mouseup', (evt) => {
						evt.preventDefault();
						evt.stopPropagation();
					})
					.on('mousedown', (evt) => {
						evt.preventDefault();
						this.clear();
						this.emit(new InputEvent('clear'));
						this.emit(new Event('change'));
						this.showClear();
					});
			}
		} else {
			this.remove(svg);
		}
	}

	private showEye() {
		const svg = this.query('svg[name=eye]');
		this.remove(svg);
		if (this.type() === 'password' && !this.disabled()) {
			const input = this.query('input');
			const clear = this.query('svg[name=clear]').takeIf() ?? input;
			dom
				.html(this[DATA].shown ? EyeIcon : EyeOffIcon)
				.name('eye')
				.appendTo(this.elems(), clear)
				.on('mousedown', (evt) => {
					evt.preventDefault();
					if (this.disabled()) return;
					this[DATA].shown = !this[DATA].shown;
					this.showEye();
				});
			if (this[DATA].shown) {
				input.attr('type', 'text');
			} else {
				input.attr('type', 'password');
			}
		}
	}

	label(): string;
	label(text: string): this;
	label(text?: string): this | string | undefined {
		if (text === undefined) {
			return this.query('label').takeIf()?.text() ?? '';
		} else {
			let lb = this.query('label').takeIf();
			if (text) {
				if (!lb) {
					lb = dom.label();
					lb.insertTo(this.elems(), this.query('input'));
				}
				lb.text(text);
			} else {
				lb?.detach();
			}
		}
	}

	disabled(): boolean;
	disabled(value: boolean): this;
	disabled(value?: boolean): boolean | this {
		if (value === undefined) {
			return this.attr('disabled') != null;
		}
		this.attr('disabled', value);
		this.query('input').attr('disabled', value);
		this.showEye();
		return this;
	}

	readonly(): boolean;
	readonly(value: boolean): this;
	readonly(value?: boolean): boolean | this {
		if (value === undefined) {
			return this.attr('readonly') != null;
		}
		this.attr('readonly', value);
		this.query('input').attr('readonly', value);
		return this;
	}

	clearable(): boolean;
	clearable(value: boolean): this;
	clearable(value?: boolean): boolean | this {
		if (value === undefined) {
			return this.attr('clearable') != null;
		}
		this.attr('clearable', value);
		this.showClear();
		return this;
	}

	prefixIcon(icon: string | null): this {
		const svg = this.query('svg[name=prefix]');
		this.remove(svg);
		if (icon) {
			this.insert(
				dom
					.html(icon)
					.name('prefix')
					.on('click', () => {
						if (this.disabled()) return;
						const evt = new Event('prefixiconclick');
						this.emit(evt);
					})
			);
		}
		return this;
	}

	suffixIcon(icon: string | null): this {
		const svg = this.query('svg[name=suffix]');
		this.remove(svg);
		if (icon) {
			this.append(
				dom
					.html(icon)
					.name('suffix')
					.on('click', () => {
						if (this.disabled()) return;
						const evt = new Event('suffixiconclick');
						this.emit(evt);
					})
			);
		}
		return this;
	}

	type(): InputType;
	type(type: InputType): this;
	type(type?: InputType): InputType | this {
		const current = (this.attr('type') ?? 'text') as InputType;
		if (type === undefined) {
			return current;
		}
		this.attr('type', type);
		this.query('input').attr('type', type);
		if (type != current) {
			this.showEye();
		}
		return this;
	}

	override text(): string;
	override text(text: string): this;
	override text(text?: string): this | string {
		const input = this.query('input');
		if (typeof text === 'string') {
			input.value(text);
			this.showClear();
			return this;
		}
		return (input.value() ?? '') as string;
	}

	override value(): unknown;
	override value(text: unknown): this;
	override value(text?: unknown): this | unknown {
		const input = this.query('input');
		if (typeof text === 'string') {
			input.value(text);
			this.showClear();
			return this;
		}
		return input.value();
	}

	override focus(options?: FocusOptions | undefined): this {
		const input = this.query('input');
		input.focus(options);
		return this;
	}

	placeholder(): string;
	placeholder(text: string): this;
	placeholder(text?: string): this | string | undefined {
		const input = this.query('input');
		if (typeof text === 'string') {
			input.prop('placeholder', text);
			return this;
		}
		return input.prop('placeholder') as string;
	}

	maxLength(): number | null;
	maxLength(len: number | null): this;
	maxLength(len?: number | null): this | number | null {
		const input = this.query('input');
		if (len !== undefined) {
			input.attr('maxlength', len ? len + '' : null);
			return this;
		}
		return takeIf(input.attr('maxlength'), (v) => parseInt(v)) ?? null;
	}

	min(): string | number | null;
	min(value: string | number | null): this;
	min(value?: string | number | null): this | string | number | null {
		const input = this.query('input');
		if (value !== undefined) {
			input.attr('min', value ? value + '' : null);
			return this;
		}
		return input.attr('min') ?? null;
	}

	max(): string | number | null;
	max(value: string | number | null): this;
	max(value?: string | number | null): this | string | number | null {
		const input = this.query('input');
		if (value !== undefined) {
			input.attr('max', value ? value + '' : null);
			return this;
		}
		return input.attr('max') ?? null;
	}

	step(): number | 'any' | null;
	step(value: number | 'any' | null): this;
	step(value?: number | 'any' | null): this | number | 'any' | null {
		const input = this.query('input');
		if (value !== undefined) {
			input.attr('step', value ? value + '' : null);
			return this;
		}
		return takeIf(input.attr('step'), (v) => (v === 'any' ? v : parseInt(v))) ?? null;
	}
}

export function input(...className: string[]): Input {
	return new Input().addClass(...className);
}
